unit DIrr.Plane3D;

{$I DIrr.inc}

interface

uses
  Math, DIrr.Math, DIrr.Vector3D;

type
  TIntersectionRelation3D =
    (
      ir3DFront,
      ir3DBack,
      ir3DPlanar,
      ir3DSpanning,
      ir3DClipped
    );

  PPlane3DF = ^TPlane3DF;
  TPlane3DF = record
    //! Normal vector of the plane.
    Normal: TVector3DF;
    //! Distance from origin.
    Distance: Single;
  public
    constructor Create(const P1, P2, P3: TVector3DF); overload;
    constructor Create(const Point, NVector: TVector3DF); overload;
    constructor Create(const NVector: TVector3DF; dis: Single); overload;
  public
    class operator Equal(Value, Other: TPlane3DF): Boolean;
    class operator NotEqual(Value, Other: TPlane3DF): Boolean;

    procedure SetPlane(const Point, NVector: TVector3DF); overload; inline;
    procedure SetPlane(const NVector: TVector3DF; dis: Single); overload; inline;
    procedure SetPlane(const P1, P2, P3: TVector3DF); overload; inline;

		//! Get an intersection with a 3d line.
		(* \param lineVect Vector of the line to intersect with.
		\param linePoint Point of the line to intersect with.
		\param outIntersection Place to store the intersection point, if there is one.
		\return True if there was an intersection, false if there was not.*)
    function GetIntersectionWithLine(const LinePoint, LineVect: TVector3DF;
      var OutIntersection: TVector3DF): Boolean;
		//! Get percentage of line between two points where an intersection with this plane happens.
		(* Only useful if known that there is an intersection.
		\param linePoint1 Point1 of the line to intersect with.
		\param linePoint2 Point2 of the line to intersect with.
		\return Where on a line between two points an intersection with this plane happened.
		For example, 0.5 is returned if the intersection happened exactly in the middle of the two points.
		*)
    function GetKnownIntersectionWithLine(const LineP1, LineP2: TVector3DF): Double;
		//! Get an intersection with a 3d line, limited between two 3d points.
		(* \param linePoint1 Point 1 of the line.
		\param linePoint2 Point 2 of the line.
		\param outIntersection Place to store the intersection point, if there is one.
		\return True if there was an intersection, false if there was not.*)
    function GetIntersectionWithLimitedLine(const LineP1, LineP2: TVector3DF;
      var OutIntersection: TVector3DF): Boolean;
		//! Classifies the relation of a point to this plane.
		(* \param point Point to classify its relation.
		\return ISREL3D_FRONT if the point is in front of the plane,
		ISREL3D_BACK if the point is behind of the plane, and
		ISREL3D_PLANAR if the point is within the plane. *)
    function ClassifyPointRelation(const Point: TVector3DF): TIntersectionRelation3D;
    //! Recalculates the distance from origin by applying a new member point to the plane.
    procedure RecalculateD(const MPoint: TVector3DF);
    //! Gets a member point of the plane.
    function GetMemberPoint: TVector3DF;
		//! Tests if there is an intersection with the other plane
		(* \return True if there is a intersection. *)
    function ExistsIntersection(const Other: TVector3DF): Boolean;
		//! Intersects this plane with another.
		(* \param other Other plane to intersect with.
		\param outLinePoint Base point of intersection line.
		\param outLineVect Vector of intersection.
		\return True if there is a intersection, false if not. *)
    function GetIntersectionWithPlane(const Other: TPlane3DF;
      var OutLinePoint, OutLineVect: TVector3DF): Boolean;
    //! Get the intersection point with two other planes if there is one.
    function GetIntersectionWithPlanes(const o1, o2: TPlane3DF;
      var OutPoint: TVector3DF): Boolean;
		//! Test if the triangle would be front or backfacing from any point.
		(* Thus, this method assumes a camera position from
		which the triangle is definitely visible when looking into
		the given direction.
		Note that this only works if the normal is Normalized.
		Do not use this method with points as it will give wrong results!
		\param lookDirection: Look direction.
		\return True if the plane is front facing and
		false if it is backfacing. *)
    function IsFrontFacing(const LookDirection: TVector3DF): Boolean;
		//! Get the distance to a point.
		(* Note that this only works if the normal is normalized. *)
    function GetDistanceTo(const Point: TVector3DF): Single;
  end;

implementation

{ TPlane3DF }

function TPlane3DF.ClassifyPointRelation(
  const Point: TVector3DF): TIntersectionRelation3D;
var
  d: Single;
begin
  d := Normal.DotProduct(Point) + Distance;

  if (d < -ROUNDING_ERROR_F32) then
  begin
    Result := ir3DBack;
    Exit;
  end;

  if (d > ROUNDING_ERROR_F32) then
  begin
    Result := ir3DFront;
    Exit;
  end;

  Result := ir3DPlanar;
end;

constructor TPlane3DF.Create(const P1, P2, P3: TVector3DF);
begin
  SetPlane(P1, P2, P3);
end;

constructor TPlane3DF.Create(const Point, NVector: TVector3DF);
begin
  SetPlane(Point, NVector);
end;

constructor TPlane3DF.Create(const NVector: TVector3DF; dis: Single);
begin
  SetPlane(NVector, dis);
end;

class operator TPlane3DF.Equal(Value, Other: TPlane3DF): Boolean;
begin
  Result := SameValue(Value.Distance, Other.Distance, ROUNDING_ERROR_F32) and
    (Value.Normal = Other.Normal);
end;

function TPlane3DF.ExistsIntersection(const Other: TVector3DF): Boolean;
var
  Cross: TVector3DF;
begin
  Other.Normalize;

  Cross := Other.CrossProduct(Normal);
  Result := Cross.GetLength > ROUNDING_ERROR_F32;
end;

function TPlane3DF.GetDistanceTo(const Point: TVector3DF): Single;
begin
  Result := Point.DotProduct(Normal) + Distance;
end;

function TPlane3DF.GetIntersectionWithLimitedLine(const LineP1,
  LineP2: TVector3DF; var OutIntersection: TVector3DF): Boolean;
begin
  Result := GetIntersectionWithLine(LineP1, LineP2 - LineP1, OutIntersection) and
    OutIntersection.IsBetweenPoints(LineP1, LineP2);
end;

function TPlane3DF.GetIntersectionWithLine(const LinePoint,
  LineVect: TVector3DF; var OutIntersection: TVector3DF): Boolean;
var
  t, t2: Single;
begin
  t2 := Normal.DotProduct(LineVect);

  if (t2 = 0) then
  begin
    Result := False;
    Exit;
  end;

  t := -(Normal.DotProduct(LinePoint) + Distance) / t2;
  OutIntersection := LinePoint + LineVect * t;
  Result := True;
end;

function TPlane3DF.GetIntersectionWithPlane(const Other: TPlane3DF;
  var OutLinePoint, OutLineVect: TVector3DF): Boolean;
var
  fn00, fn01, fn11: Single;
  det: Double;
  invdet, fc0, fc1: Double;
begin
  fn00 := Normal.GetLength;
  fn01 := Normal.DotProduct(Other.Normal);
  fn11 := Other.Normal.GetLength;
  det := fn00 * fn11 - fn01 * fn01;

  if (Abs(det) < ROUNDING_ERROR_F64) then
  begin
    Result := False;
    Exit;
  end;

  invdet := 1.0 / det;
  fc0 := (fn11 * (-Distance) + fn01 * Other.Distance) * invdet;
  fc1 := (fn00 * (-Other.Distance) + fn01 * Distance) * invdet;

  OutLineVect := Normal.CrossProduct(Other.Normal);
  OutLinePoint := Normal * fc0 + Other.Normal * fc1;
  Result := True;
end;

function TPlane3DF.GetIntersectionWithPlanes(const o1, o2: TPlane3DF;
  var OutPoint: TVector3DF): Boolean;
var
  LinePoint, LineVect: TVector3DF;
begin
  if GetIntersectionWithPlane(o1, LinePoint, LineVect) then
  begin
    Result := o2.GetIntersectionWithLine(LinePoint, LineVect, OutPoint);
    Exit;
  end;
  Result := False;
end;

function TPlane3DF.GetKnownIntersectionWithLine(const LineP1,
  LineP2: TVector3DF): Double;
var
  vect: TVector3DF;
  t2: Single;
begin
  vect := LineP2 - LineP1;
  t2 := Normal.DotProduct(vect);
  Result := -(Normal.DotProduct(LineP1) + Distance) / t2;
end;

function TPlane3DF.GetMemberPoint: TVector3DF;
begin
  Result := Normal * (-Distance);
end;

function TPlane3DF.IsFrontFacing(const LookDirection: TVector3DF): Boolean;
var
  d: Double;
begin
  d := Normal.DotProduct(LookDirection);
  Result := (d <= 0);
end;

class operator TPlane3DF.NotEqual(Value, Other: TPlane3DF): Boolean;
begin
  Result := not (Value = Other);
end;

procedure TPlane3DF.RecalculateD(const MPoint: TVector3DF);
begin
  Distance := -MPoint.DotProduct(Normal);
end;

procedure TPlane3DF.SetPlane(const Point, NVector: TVector3DF);
begin
  Normal := NVector;
  RecalculateD(Point);
end;

procedure TPlane3DF.SetPlane(const NVector: TVector3DF; dis: Single);
begin
  Normal := NVector;
  Distance := dis;
end;

procedure TPlane3DF.SetPlane(const P1, P2, P3: TVector3DF);
begin
  // creates the plane from 3 memberpoints
  Normal := (P2 - P1).CrossProduct(P3 - P1);
  Normal.Normalize;

  RecalculateD(P1);
end;

end.
